#!/usr/bin/python

# InnerSpace
# Copyright (C) 1999-2006 Phil Christensen
#
# $Id: inner_space_server.py 127 2007-01-15 17:06:51Z phil $
#
# See LICENSE for details

# from twisted.scripts.mktap import _tapHelper
# 
# InnerSpaceServer = _tapHelper(
#     "InnerSpace Server",
#     "inner.space.bigbang",
#     "Builds an innerspace server.",
#     "innerspace")

from zope.interface import implements

from twisted.python import usage
from twisted.plugin import IPlugin
from twisted.application import internet, service

from twisted.spread import pb
from twisted.cred import portal, checkers, credentials
from twisted.conch import manhole_ssh
from twisted.internet import reactor

# try:
# 	from twisted.internet import ssl
# 	ssl_broken = False
# except:
# 	ssl_broken = True
	
from inner.space import auth, external, registry, webclient, pbclient

from nevow import appserver, guard

import os

class Options(usage.Options):
	"""
	We allow the user to specify port settings and SSL settings. To use SSL
	the user must have pyOpenSSL installed.
	"""
	optParameters = [["port" ,"P", 8420, "Port to use for PB server."],
					 ["datafile" ,"f", 'universe.xml', "An XML datafile to bootstrap with."],
					 ["verbdir" ,"v", 'verbs', "A directory of verbs to bootstrap with."],
					 ["ssh-port" ,"S", 4040, "Port to use for SSH/manhole server."],
					 ["user" ,"u", 'admin', "Manhole username."],
					 ["pass" ,"p", '4dm1nus3r', "Manhole password."],
					 ["web-port" ,"W", 8080, "Port to use for web server."],
					 ["webroot" ,"w", 'webroot', "Path to web support directory."],
					 ["key", "k", "/System/Library/OpenSSL/certs/innerspace.key", "OpenSSL private key filename"],
					 ["cert", "c", "/System/Library/OpenSSL/certs/innerspace.crt", "OpenSSL certificate filename"]
					]
	optFlags = [["secure", "s", "Require OpenSSL connection"]]

class InnerSpaceServerFactory(pb.PBServerFactory):
    def buildProtocol(self, addr):
		"""Return a Broker attached to me (as the service provider).
		"""
		system = auth.get_registry().get(0)
		if((not system.has_verb('connect')) or system.call_verb('connect', addr.host)):
			return pb.PBServerFactory.buildProtocol(self, addr)
		return None

class InnerSpaceServer(object):
	implements(service.IServiceMaker, IPlugin)
	tapname = "innerspace"
	description = "Run an innerspace server."
	options = Options
	
	def save(self, reg, path):
		datafile = file(path, 'w')
		external.export(reg, datafile)
		datafile.close()
	
	def load(self, reg, path):
		if not(os.access(path, os.R_OK)):
			return False
		datafile = file(path)
		external.load(reg, datafile)
		datafile.close()
		return True
	
	def makeService(self, config):
		master_service = service.MultiService()
		auth.set_master_service(master_service)
		
		#The registry service basically just holds our registry reference
		#for safe-keeping
		reg_service = auth.RegistryService(registry.Registry(True))
		reg_service.setName("registry")
		reg_service.setServiceParent(master_service)
		
		path = config['datafile']
		
		if(os.access(path + '.shutdown', os.F_OK)):
			if(os.access(path, os.F_OK)):
				if(os.stat(path + '.shutdown').st_mtime >= os.stat(path).st_mtime):
					os.rename(path, path + '.bak')
			os.rename(path + '.shutdown', path)
		
		if(not path):
			save_path = 'universe.xml'
		else:
			save_path = path + '.shutdown'
		
		if not(self.load(reg_service.registry, path)):
			reg_service.registry = registry.Registry()
			from inner.space import minimal
			minimal.init(reg_service.registry, config['verbdir'])
		
		reactor.addSystemEventTrigger('before', 'shutdown', self.save, reg_service.registry, save_path)
		
		#Setup the authentication framework
		checker = auth.RegistryChecker(reg_service.registry)

		registry_portal = portal.Portal(auth.InnerSpaceRealm(reg_service, config['webroot']))
		registry_portal.registerChecker(checkers.AllowAnonymousAccess(), credentials.IAnonymous)
		registry_portal.registerChecker(checker)
		
		# this was a mistake, but it happens to work
		# this would, however, affect ALL PB connections in this memory space,
		#   so it should probably be changed soon.
		pb.PBServerFactory.protocol = pbclient.InnerSpaceBroker
		innerspace_factory = InnerSpaceServerFactory(registry_portal)
		
		manhole_factory = manhole_ssh.ConchFactory(registry_portal)
		web_factory = appserver.NevowSite(guard.SessionWrapper(registry_portal))

		#If the user has selected secure, we only allow connections
		#over SSL. The pyOpenSSL library must be installed for this
		#to work.
		if(config['secure']):
			raise RuntimeError("Sorry, SSL is not supported at this time.")
			# this is just one of those things i never got around to fixing again....
			#if(ssl_broken):
			#	raise RuntimeError("Sorry, SSL is not supported on this platform (possibly you are missing the OpenSSL module?)")
			#contexts = ssl.DefaultOpenSSLContextFactory(config['key'], config['cert'])
			#pb_service = internet.SSLServer(int(config['port']), pb.PBServerFactory(registry_portal), contexts)
			#manhole_service = internet.SSLServer(4040, manhole_factory, contexts)
		else:
			pb_service = internet.TCPServer(int(config['port']), innerspace_factory)
			manhole_service = internet.TCPServer(int(config['ssh-port']), manhole_factory)
			web_service = internet.TCPServer(int(config['web-port']), web_factory)
		
		pb_service.setServiceParent(master_service)
		manhole_service.setServiceParent(master_service)
		web_service.setServiceParent(master_service)		
		
		return master_service

serviceMaker = InnerSpaceServer()